﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using SilverTile;
using System.Linq;
using SilverTile.Sprites;
using SilverTile.Viewports;

namespace SilverTileTests
{

    [TestClass]
    public class CollisionDetectionTests
    {

        [TestMethod]
        public void CollidesWith_Should_Return_The_Correct_Enum_Values()
        {

            ISprite sprite1 = new Sprite(50, 50) { X = 100, Y = 100 }; // x1=50, x2 = 150, y1=50, y2=150
            ISprite sprite2 = new Sprite(20, 20);

            Assert.AreEqual(Collision.None, sprite1.CollidesWithDir(sprite2), "Sprite1 CollidesWith sprite2 should return Collision.None");

            sprite2.X = 90; // x1=90, x2 = 110, 
            sprite2.Y = 120; // y1=120, y2=140
            Assert.IsTrue(sprite1.CollidesWithDir(sprite2).IsSet(Collision.Left), "Should return Collision.Left");
            
            sprite2.X = 140;
            sprite2.Y = 120;
            Assert.IsTrue(sprite1.CollidesWithDir(sprite2).IsSet(Collision.Right), "Should return Collision.Right");

            sprite2.X = 120;
            sprite2.Y = 90;
            Assert.IsTrue(sprite1.CollidesWithDir(sprite2).IsSet(Collision.Top), "Should return Collision.Top");

            sprite2.X = 120;
            sprite2.Y = 140;
            Assert.IsTrue(sprite1.CollidesWithDir(sprite2).IsSet(Collision.Bottom), "Should return Collision.Bottom");

            sprite2.X = 90;
            sprite2.Y = 90;
            Collision coll = sprite2.CollidesWithDir(sprite1);
            Assert.IsTrue(coll.IsSet(Collision.Right), "should collide with right");
            Assert.IsTrue(coll.IsSet(Collision.Bottom), "should collide with bottom");

            sprite2.X = 90;
            sprite2.Y = 120;
            coll = sprite2.CollidesWithDir(sprite1);
            Assert.IsTrue(coll.IsSet(Collision.Right), "should collide with right");
            Assert.IsTrue(coll.IsSet(Collision.Bottom), "should collide with bottom");
            Assert.IsTrue(coll.IsSet(Collision.Top), "should collide with top");

            sprite2.X = 90;
            sprite2.Y = 140;
            coll = sprite2.CollidesWithDir(sprite1);
            Assert.IsTrue(coll.IsSet(Collision.Right), "should collide with right");
            Assert.IsTrue(coll.IsSet(Collision.Top), "should collide with top");

            sprite2.X = 140;
            sprite2.Y = 120;
            coll = sprite2.CollidesWithDir(sprite1);
            Assert.IsTrue(coll.IsSet(Collision.Left), "should collide with left");
            Assert.IsTrue(coll.IsSet(Collision.Bottom), "should collide with bottom");
            Assert.IsTrue(coll.IsSet(Collision.Top), "should collide with top");


        }


        [TestMethod]
        public void Two_Overlapping_Sprites_Should_Return_Collides_Result()
        {

            ISprite sprite1 = new Sprite(30, 30) { X = 10, Y = 10 };
            ISprite sprite2 = new Sprite(30, 30) { X = 20, Y = 20 };

            Assert.IsTrue(sprite1.CollidesWith(sprite2), "Sprite 1 should collide with sprite 2");
            Assert.IsTrue(sprite2.CollidesWith(sprite1), "Sprite 2 should collide with sprite 1");

        }
        
        [TestMethod]
        public void Collision_Test_Should_Allow_Arbitrary_Locations()
        {

            ISprite sprite1 = new Sprite(30, 30) { X = 100, Y = 100 };
            ISprite sprite2 = new Sprite(30, 30) { X = 10, Y = 10 };

            Assert.IsTrue(sprite1.CollidesWith(sprite2, 20, 20, 0), "Sprite 1 at 20,20 should collide with sprite 2");

        }

        [TestMethod]
        public void Two_Not_Overlapping_Sprites_Should_Return_Collides_False()
        {

            ISprite sprite1 = new Sprite(30, 30) { X = 10, Y = 10 };
            ISprite sprite2 = new Sprite(30, 30) { X = 100, Y = 20 };

            Assert.IsFalse(sprite1.CollidesWith(sprite2), "Sprite 1 should not collide with sprite 2");
            Assert.IsFalse(sprite2.CollidesWith(sprite1), "Sprite 2 should not collide with sprite 1");

        }

        [TestMethod]
        public void Collision_Detection_Should_Allow_Bounding_Boxes_To_Expand_And_Shrink()
        {

            ISprite sprite1 = new Sprite(30, 30) { X = 10, Y = 10 };
            ISprite sprite2 = new Sprite(30, 30) { X = 40, Y = 20 };

            // The sprites doesn't overlap, but we expand their bounding boxes by 1 pixel
            Assert.IsTrue(sprite1.CollidesWith(sprite2, sprite2.X, sprite2.Y, 1), "Sprite 1 should collide with sprite 2 when bounding box is expanded by 1 pixel");

            sprite2.X = 39;
            // The sprites overlap but we shrink their bounding boxes by 1 pixel
            Assert.IsFalse(sprite1.CollidesWith(sprite2, sprite1.X, sprite1.Y, -1), "Sprite 1 should not collide with sprite 2 when bounding box is shrunk by 1 pixel");
            
        }

        [TestMethod]
        public void Viewport_Should_Give_All_Colliding_Foreground_Sprites()
        {

            ISprite sprite1 = new Sprite(30, 30) { X = 10, Y = 10 };
            ISprite sprite2 = new Sprite(30, 30) { X = 10, Y = 20 };
            ISprite sprite3 = new Sprite(30, 30) { X = 10, Y = 30 };

            IViewport viewport = new Viewport(100, 100);
            viewport.AddSprite("s1", sprite1);
            viewport.AddSprite("s2", sprite2);
            viewport.AddSprite("s3", sprite3);

            var collidesWith = viewport.GetForegroundSpriteCollisions(sprite1).ToList();

            Assert.AreEqual(2, collidesWith.Count, "Sprite 1 should collide with two other sprites");

            Assert.IsTrue(collidesWith.Contains(sprite2), "Sprite 1 should collide with sprite 2");
            Assert.IsTrue(collidesWith.Contains(sprite3), "Sprite 1 should collide with sprite 3");

        }

        [TestMethod]
        public void Viewport_Should_Give_All_Colliding_Foreground_Sprites_When_Expanded()
        {

            ISprite sprite1 = new Sprite(30, 30) { X = 10, Y = 10 };
            ISprite sprite2 = new Sprite(30, 30) { X = 10, Y = 20 };
            ISprite sprite3 = new Sprite(30, 30) { X = 10, Y = 41 };

            IViewport viewport = new Viewport(100, 100);
            viewport.AddSprite("s1", sprite1);
            viewport.AddSprite("s2", sprite2);
            viewport.AddSprite("s3", sprite3);

            var collidesWith = viewport.GetForegroundSpriteCollisions(sprite1,1).ToList();

            Assert.AreEqual(2, collidesWith.Count, "Sprite 1 should collide with two other sprites");

            Assert.IsTrue(collidesWith.Contains(sprite2), "Sprite 1 should collide with sprite 2");
            Assert.IsTrue(collidesWith.Contains(sprite3), "Sprite 1 should collide with sprite 3");

        }

        [TestMethod]
        public void Viewport_Should_Return_True_If_A_Sprite_Will_Collide_With_A_Background_Sprite_At_A_Location()
        {

            ISprite bgSprite = new Sprite(30, 30) { Blocking = true };
            IViewport viewport = new Viewport(100, 100);
            viewport.AddBackgroundSprite(bgSprite, 0, 0);

            ISprite sprite = new Sprite(30, 30) { X = 25, Y = 25 };
            viewport.AddSprite("collide", sprite);

            Assert.AreEqual(Collision.Left | Collision.Top, viewport.SpriteWillCollideWithBackground(sprite, 25, 25,0), "Sprite should collide with background at the given location");
            Assert.AreEqual(Collision.None, viewport.SpriteWillCollideWithBackground(sprite, 50, 50,0), "Sprite should not collide with background at the given location");

        }

        [TestMethod]
        public void Sprite_With_Blocking_False_Should_Not_Cause_Background_Collisions()
        {

            ISprite bgSprite = new Sprite(30, 30) { Blocking = false };
            IViewport viewport = new Viewport(100, 100);
            viewport.AddBackgroundSprite(bgSprite, 0, 0);

            ISprite sprite = new Sprite(30, 30) { X = 50, Y = 50 };
            viewport.AddSprite("collide", sprite);

            Assert.AreEqual(Collision.None, viewport.SpriteWillCollideWithBackground(sprite, 10, 10,0), "Sprite should not collide with background");

        }

        [TestMethod]
        public void Collision_Should_Use_Bounding_Box_For_Collisions()
        {
            
            IFrame frame = new Frame("/SilverTileTests;component/Tiles/0002.png"); // 32x32 px
            frame.Bounds = new Rect(5, 5, 20, 20); // Limit the bounding box of the frame. Origin is now at 0,0

            var sprite1 = new Sprite(frame)
            {
                X = 10,
                Y = 10
            }; // Left = 10, Top = 10, Right = 10 + 20 = 30, Bottom = 10 + 20 = 30

            var sprite2 = new Sprite(20, 20)
            {
                X = 35,
                Y = 35
            };

            // Sprite 2 is now inside the visible area of sprite 1 but outside it's set bounding box
                        
            Assert.IsFalse(sprite1.CollidesWith(sprite2), "Sprite1 should not collide with sprite2");

            sprite2.X = 25;
            sprite2.Y = 25; // Now they should collide
            Assert.IsTrue(sprite1.CollidesWith(sprite2), "Sprite1 at " + sprite1.Area + " should collide with sprite2 at " + sprite2.Area);

        }

        [TestMethod]
        public void Background_collisions_should_be_checked_against_Background_Bounding_Box()
        {

            ISprite bgSprite = new Sprite(30, 30) { Blocking = true };
            IViewport viewport = new Viewport(100, 100);
            viewport.AddBackgroundSprite(bgSprite, 0, 0);

            IFrame frame = new Frame("/SilverTileTests;component/Tiles/0002.png"); // 32x32 px
            frame.BackgroundBounds = new Rect(0, 16, 32, 16); // Background bounding box is only for the lower half of the sprite
            ISprite sprite = new Sprite(frame) {X = 0, Y = 16}; // Sprite goes from Y = 16 to Y = 16 + 32 = 48, but BgBounds only goes from Y = 32 to Y = 48
            
            viewport.AddSprite("collide", sprite);

            Assert.AreEqual(Collision.None, viewport.SpriteWillCollideWithBackground(sprite, sprite.X, sprite.Y, 0), "Sprite should not collide with background at the given location");
            Assert.AreEqual(Collision.Top | Collision.Left, viewport.SpriteWillCollideWithBackground(sprite, 10, 8, 0), "Sprite should collide with background at the given location");

        }

    }
}
